/**
 * This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
 */
package com.softwarecraftsmen.orogeny.actions.zip.signing;

import com.softwarecraftsmen.encoding.base64.Base64Encoder;
import static com.softwarecraftsmen.inputStreamReaders.Sha1MessageDigest.newSha1Digest;
import static com.softwarecraftsmen.orogeny.actions.zip.signing.ManifestDigester.ManifestMainAttributesHeader;
import com.softwarecraftsmen.archivers.zip.zipEntryAndData.ByteBackedZipEntryAndData;
import com.softwarecraftsmen.archivers.zip.zipEntryAndData.ZipEntryAndData;
import com.softwarecraftsmen.outputStreamWriters.ManifestOutputStreamWriter;
import org.jetbrains.annotations.NotNull;

import static java.lang.String.format;
import static java.lang.System.getProperty;
import java.security.MessageDigest;
import static java.util.Locale.UK;
import java.util.Map;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.Attributes.Name;
import static java.util.jar.Attributes.Name.SIGNATURE_VERSION;
import java.util.jar.Manifest;

public class SignatureFile
{
	private static final Base64Encoder Base64Encoder = new Base64Encoder();
	private static final Name CreatedBy = new Name("Created-By");
	private static final String JavaVersion = getProperty("java.version");
	private static final String JavaVendor = getProperty("java.vendor");

	private final MessageDigest messageDigest;
	private final ManifestDigester manifestDigester;
	private final Manifest signatureFileStructure;

	@NotNull
	public static SignatureFile sha1SignatureFile(final @NotNull Manifest manifestWithDigests)
	{
		final byte[] manifestWithDigestsContent = new ManifestOutputStreamWriter(manifestWithDigests).toByteArray();
		final ManifestDigester manifestDigester = new ManifestDigester(manifestWithDigestsContent);
		return new SignatureFile(newSha1Digest(), manifestWithDigests, manifestDigester);
	}

	public SignatureFile(final @NotNull MessageDigest messageDigest, final @NotNull Manifest manifest, final @NotNull ManifestDigester manifestDigester)
	{
		this.messageDigest = messageDigest;
		this.manifestDigester = manifestDigester;
		signatureFileStructure = new Manifest();

		computeDigests(manifest, messageDigest.getAlgorithm());
	}

	@NotNull
	public byte[] addToEntriesAndReturnContent(final @NotNull Set<ZipEntryAndData> allEntries, final long modificationTimeInMillisecondsSinceEpoch, final @NotNull String signatureFileName)
	{
		final byte[] content = content();
		allEntries.add(new ByteBackedZipEntryAndData(signatureFileName, modificationTimeInMillisecondsSinceEpoch, content));
		return content;
	}

	@NotNull
	private byte[] content()
	{
		return new ManifestOutputStreamWriter(signatureFileStructure).toByteArray();
	}

	private void computeDigests(final Manifest manifest, final String algorithmName)
	{
		final Attributes mainAttributes = signatureFileStructure.getMainAttributes();
		mainAttributes.put(SIGNATURE_VERSION, "1.0");
		mainAttributes.put(CreatedBy, format(UK, "%1$s (%2$s)", JavaVersion, JavaVendor));
		mainAttributes.putValue(format(UK, "%1$s-Digest-Manifest", algorithmName), encodeManifestDigest());
		mainAttributes.putValue(format(UK, "%1$s-Digest-Manifest-Main-Attributes", algorithmName), encodeManifestMainAttributesDigest());
		computeEachEntrysDigest(manifest, algorithmName);
	}

	private void computeEachEntrysDigest(final Manifest manifest, final String algorithmName)
	{
		final Map<String, Attributes> signatureFileEntries = signatureFileStructure.getEntries();
		for (Map.Entry<String, Attributes> manifestEntry : manifest.getEntries().entrySet())
		{
			final String key = manifestEntry.getKey();
			final ManifestDigesterEntry entry = manifestDigester.get(key);
			signatureFileEntries.put(key, new Attributes()
			{{
				putValue(format(UK, "%1$s-Digest", algorithmName), encode(entry));
			}});
		}
	}

	private String encodeManifestDigest() {return encode(manifestDigester.manifestDigest());}

	private String encodeManifestMainAttributesDigest() {return encode(manifestDigester.get(ManifestMainAttributesHeader));}

	private String encode(final ManifestDigesterEntry entry) {return encode(entry.digest(messageDigest));}

	private String encode(final byte[] data)
	{
		return Base64Encoder.encode(data);
	}
}
